﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using TiffLibrary;
using WholeSlideImageLibrary.DeepZoom;
using WholeSlideImageLibrary.Features;

namespace WholeSlideImageLibrary.Formats.GenericTiff
{
    internal sealed class GenericTiffWholeSlideImage : WholeSlideImage, IDeepZoomProfileHintFeature, IAssociatedImagesFeature
    {
        private FileSourceReaderCache? _cache;

        private GenericTiffLevelDecoder[]? _levels;
        private Dictionary<string, GenericTiffLevelDecoder> _associatedImages;

        private int _tileSize;

        internal GenericTiffWholeSlideImage(FileSourceInfo source)
        {
            _cache = new FileSourceReaderCache(source);
            _associatedImages = new Dictionary<string, GenericTiffLevelDecoder>(StringComparer.Ordinal);
        }

        internal async Task InitializeAsync(bool identityOnly, CancellationToken cancellationToken)
        {
            FileSourceReaderCache? cache = _cache;
            Debug.Assert(cache != null);

            TiffFileReader? tiff = await TiffFileReader.OpenAsync(new FileSourceReaderCacheContentSource(cache!), cancellationToken: cancellationToken).ConfigureAwait(false);
            List<GenericTiffLevelDecoder>? levels = identityOnly ? null : new List<GenericTiffLevelDecoder>(24);

            TiffFieldReader? fieldReader = null;
            TiffTagReader tagReader;
            uint tileSize = 0;
            try
            {
                fieldReader = await tiff.CreateFieldReaderAsync(cancellationToken).ConfigureAwait(false);

                // 最大的层
                TiffImageFileDirectory ifd = await tiff.ReadImageFileDirectoryAsync(cancellationToken).ConfigureAwait(false);
                TiffImageDecoder decoder = await tiff.CreateImageDecoderAsync(ifd, cancellationToken).ConfigureAwait(false);
                tagReader = new TiffTagReader(fieldReader, ifd);
                TiffPhotometricInterpretation? photometricInterpretation = await tagReader.ReadPhotometricInterpretationAsync(cancellationToken).ConfigureAwait(false);
                TiffCompression compression = await tagReader.ReadCompressionAsync(cancellationToken).ConfigureAwait(false);
                uint? tileWidth = await tagReader.ReadTileWidthAsync(cancellationToken).ConfigureAwait(false);
                uint? tileLength = await tagReader.ReadTileLengthAsync(cancellationToken).ConfigureAwait(false);
                tileSize = tileWidth.HasValue ? tileWidth.GetValueOrDefault() : 256;
                var level0 = new GenericTiffLevelDecoder(decoder, 1);
                level0.SupportFastJpegReading = photometricInterpretation == TiffPhotometricInterpretation.YCbCr && compression == TiffCompression.Jpeg && tileWidth.GetValueOrDefault() == tileSize && tileLength.GetValueOrDefault() == tileSize;
                levels?.Add(level0);
                GenericTiffLevelDecoder? lastLevel = level0;

                if (lastLevel.SupportFastJpegReading)
                {
                    lastLevel.Offsets = (await tagReader.ReadTileOffsetsAsync(cancellationToken).ConfigureAwait(false)).Select(o => (long)o).ToArray();
                    lastLevel.ByteCounts = (await tagReader.ReadTileByteCountsAsync(cancellationToken).ConfigureAwait(false)).Select(c => (int)c).ToArray();
                    lastLevel.JpegTables = await tagReader.ReadJPEGTablesAsync(cancellationToken).ConfigureAwait(false);
                    if (lastLevel.Offsets.Length == 0 || lastLevel.Offsets.Length != lastLevel.ByteCounts.Length)
                    {
                        lastLevel.SupportFastJpegReading = false;
                        lastLevel.Offsets = null;
                        lastLevel.ByteCounts = null;
                        lastLevel.JpegTables = null;
                    }
                }

                // 后续层
                for (TiffStreamOffset ifdOffset = ifd.NextOffset; !ifdOffset.IsZero; ifdOffset = ifd.NextOffset)
                {
                    ifd = await tiff.ReadImageFileDirectoryAsync(ifdOffset, cancellationToken).ConfigureAwait(false);
                    tagReader = new TiffTagReader(fieldReader, ifd);

                    TiffNewSubfileType newSubfileType = await tagReader.ReadNewSubfileTypeAsync(cancellationToken).ConfigureAwait(false);
                    string? imageDescription = await tagReader.ReadImageDescriptionAsync(cancellationToken).ConfigureAwait(false);

                    if ("macro".Equals(imageDescription, StringComparison.OrdinalIgnoreCase))
                    {
                        if (!_associatedImages.ContainsKey("macro"))
                        {
                            decoder = await tiff.CreateImageDecoderAsync(ifd, cancellationToken).ConfigureAwait(false);
                            _associatedImages.Add("macro", new GenericTiffLevelDecoder(decoder, 1));
                        }
                    }
                    else if ("label".Equals(imageDescription, StringComparison.OrdinalIgnoreCase))
                    {
                        if (!_associatedImages.ContainsKey("label"))
                        {
                            decoder = await tiff.CreateImageDecoderAsync(ifd, cancellationToken).ConfigureAwait(false);
                            _associatedImages.Add("label", new GenericTiffLevelDecoder(decoder, 1));
                        }
                    }
                    else if ("thumbnail".Equals(imageDescription, StringComparison.OrdinalIgnoreCase))
                    {
                        if (!_associatedImages.ContainsKey("thumbnail"))
                        {
                            decoder = await tiff.CreateImageDecoderAsync(ifd, cancellationToken).ConfigureAwait(false);
                            _associatedImages.Add("thumbnail", new GenericTiffLevelDecoder(decoder, 1));
                        }
                    }
                    else if ((newSubfileType & TiffNewSubfileType.ReducedResolution) != 0)
                    {
                        decoder = await tiff.CreateImageDecoderAsync(ifd, cancellationToken).ConfigureAwait(false);

                        photometricInterpretation = await tagReader.ReadPhotometricInterpretationAsync(cancellationToken).ConfigureAwait(false);
                        compression = await tagReader.ReadCompressionAsync(cancellationToken).ConfigureAwait(false);
                        tileWidth = await tagReader.ReadTileWidthAsync(cancellationToken).ConfigureAwait(false);
                        tileLength = await tagReader.ReadTileLengthAsync(cancellationToken).ConfigureAwait(false);

                        if (decoder.Width < lastLevel.Width && decoder.Height < lastLevel.Height)
                        {
                            double downsample = Math.Max(level0.Width / (double)decoder.Width, level0.Height / (double)decoder.Height);
                            lastLevel = new GenericTiffLevelDecoder(decoder, downsample);
                            lastLevel.SupportFastJpegReading = photometricInterpretation == TiffPhotometricInterpretation.YCbCr && compression == TiffCompression.Jpeg && tileWidth.GetValueOrDefault() == tileSize && tileLength.GetValueOrDefault() == tileSize;
                            levels?.Add(lastLevel);

                            if (lastLevel.SupportFastJpegReading)
                            {
                                lastLevel.Offsets = (await tagReader.ReadTileOffsetsAsync(cancellationToken).ConfigureAwait(false)).Select(o => (long)o).ToArray();
                                lastLevel.ByteCounts = (await tagReader.ReadTileByteCountsAsync(cancellationToken).ConfigureAwait(false)).Select(c => (int)c).ToArray();
                                lastLevel.JpegTables = await tagReader.ReadJPEGTablesAsync(cancellationToken).ConfigureAwait(false);
                                if (lastLevel.Offsets.Length == 0 || lastLevel.Offsets.Length != lastLevel.ByteCounts.Length)
                                {
                                    lastLevel.SupportFastJpegReading = false;
                                    lastLevel.Offsets = null;
                                    lastLevel.ByteCounts = null;
                                    lastLevel.JpegTables = null;
                                }
                            }
                        }
                    }
                }
            }
            finally
            {
                if (!(fieldReader is null))
                {
                    await fieldReader.DisposeAsync().ConfigureAwait(false);
                }

                await tiff.DisposeAsync().ConfigureAwait(false);
            }

            if (!identityOnly)
            {
                Debug.Assert(levels != null);
                _levels = levels!.ToArray();

                // 只要最大层支持快速JPEG读取，就使用快速DeepZoom生成
                _tileSize = levels[0].SupportFastJpegReading ? (int)tileSize : 0;
            }
        }

        public override int LevelCount => EnsureNotDisposed().Length;

        public override int Width => EnsureNotDisposed()[0].Width;

        public override int Height => EnsureNotDisposed()[0].Height;

        public override Size GetLevelSize(int level)
        {
            GenericTiffLevelDecoder[] levels = EnsureNotDisposed();
            if ((uint)level >= levels.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(level));
            }
            GenericTiffLevelDecoder levelDecoder = levels[level];
            return new Size(levelDecoder.Width, levelDecoder.Height);
        }

        public override async ValueTask ReadRegionAsync<T>(int level, int x, int y, int width, int height, Memory<T> buffer, CancellationToken cancellationToken)
        {
            GenericTiffLevelDecoder[] levels = EnsureNotDisposed();

            if ((uint)level > (uint)levels.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(level));
            }
            GenericTiffLevelDecoder? levelDecoder = levels[level];
            if (Unsafe.SizeOf<T>() * buffer.Length < 4 * width * height)
            {
                throw new ArgumentException("destination is too small.", nameof(buffer));
            }

            await levelDecoder.ReadRegionAsync(x, y, width, height, buffer, cancellationToken).ConfigureAwait(false);
        }

        public IEnumerable<DeepZoomProfile> GetDeepZoomProfileHints()
        {
            if (_tileSize != 0)
            {
                yield return new DeepZoomProfile((short)_tileSize, 0, EncodedImageFileFormat.Jpeg);
            }
        }

        public ValueTask<DeepZoomTileSource?> OpenDeepZoomAsync(DeepZoomProfile profile, CancellationToken cancellationToken = default)
        {
            FileSourceReaderCache? cache = _cache;
            if (cache is null)
            {
                ThrowObjectDisposedException();
            }
            if (_tileSize != 0 && profile.TileSize == _tileSize && profile.Overlap == 0 && profile.FileFormat == EncodedImageFileFormat.Jpeg)
            {
                return new ValueTask<DeepZoomTileSource?>(new GenericTiffDeepZoomTileSource(new FileSourceReaderCacheContentSource(cache), _levels!, profile.TileSize));
            }
            return default;
        }

        public ValueTask<IEnumerable<string>> GetAllAssociatedImagesAsync(CancellationToken cancellationToken = default)
        {
            return new ValueTask<IEnumerable<string>>(_associatedImages.Keys);
        }

        public ValueTask<Size> GetAssociatedImageSizeAsync(string name, CancellationToken cancellationToken = default)
        {
            if (!_associatedImages.TryGetValue(name, out GenericTiffLevelDecoder? levelDecoder))
            {
                throw new KeyNotFoundException();
            }
            return new ValueTask<Size>(new Size(levelDecoder.Width, levelDecoder.Height));
        }

        public async ValueTask ReadAssociatedImageAsync<T>(string name, Memory<T> buffer, CancellationToken cancellationToken = default) where T : unmanaged
        {
            if (!_associatedImages.TryGetValue(name, out GenericTiffLevelDecoder? levelDecoder))
            {
                throw new KeyNotFoundException();
            }
            if (Unsafe.SizeOf<T>() * buffer.Length < 4 * levelDecoder.Width * levelDecoder.Height)
            {
                throw new ArgumentException("destination is too small.", nameof(buffer));
            }
            await levelDecoder.ImageDecoder.DecodeAsync(new WholeSlideImagePixelBuffer<T>(buffer, levelDecoder.Width, levelDecoder.Height), cancellationToken).ConfigureAwait(false);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private GenericTiffLevelDecoder[] EnsureNotDisposed()
        {
            GenericTiffLevelDecoder[]? levels = _levels;
            if (levels is null)
            {
                ThrowObjectDisposedException();
            }
            return levels;
        }

        [DoesNotReturn]
        private static void ThrowObjectDisposedException()
        {
            throw new ObjectDisposedException(nameof(GenericTiffWholeSlideImage));
        }

        public override async ValueTask DisposeAsync()
        {
            _levels = null;

            FileSourceReaderCache? cache = _cache;
            if (!(cache is null))
            {
                _cache = null;
                await cache.DisposeAsync().ConfigureAwait(false);
            }

            await base.DisposeAsync().ConfigureAwait(false);
        }

    }
}
